package com.bgi.flutterbluetooth;

import android.annotation.TargetApi;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Build;
import android.os.Handler;
import android.widget.Toast;

import androidx.annotation.NonNull;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import io.flutter.Log;
import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.embedding.engine.plugins.activity.ActivityAware;
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
import io.flutter.plugin.common.EventChannel;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
import io.flutter.plugin.common.PluginRegistry.Registrar;

/** FlutterbluetoothPlugin */
public class FlutterbluetoothPlugin implements FlutterPlugin, MethodCallHandler, EventChannel.StreamHandler {
  /// The MethodChannel that will the communication between Flutter and native Android
  ///
  /// This local reference serves to register the plugin with the Flutter Engine and unregister it
  /// when the Flutter Engine is detached from the Activity
  private static final String TAG = "FlutterbluetoothPlugin";

  private Context context;
  private Handler uiHandler;
  private MethodChannel channel;
  private EventChannel eventChannel;
  private EventChannel.EventSink mEventSink;

  private BluetoothAdapter mBluetoothAdapter;
  private Map<String, BluetoothDevice> mapBt;
  private BluetoothDevice mConnectedDevice;
  private BroadcastReceiver mReceiver;

  private BluetoothSocket mBtSocket;

  //ble
  private BluetoothGattCallback mBleCallBack;
  private BluetoothGatt mGatt;
  private String serviceUuid, characteristicUuid, descriptorUuid;

  @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
  private void init(Context context) {
    Log.d(TAG, "**********init*************");
    this.context = context;
    uiHandler = new Handler();
    mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    mapBt = new HashMap<>();
    mReceiver = new BroadcastReceiver() {
      @Override
      public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        Log.d(TAG, "广播接受action：" + action);
        switch (action) {
          case BluetoothDevice.ACTION_FOUND:
            pushDevice(intent);
            break;
          case BluetoothAdapter.ACTION_DISCOVERY_STARTED:
            pushScanState(BluetoothMsg.SCAN_STATE_START);
            break;
          case BluetoothAdapter.ACTION_DISCOVERY_FINISHED:
            pushScanState(BluetoothMsg.SCAN_STATE_OVER);
            break;
          case BluetoothAdapter.ACTION_STATE_CHANGED://蓝牙状态改变为：10 关闭 11 正在打开 12 打开 13 正在关闭
            pushBlueToothState(intent);
            break;
        }
      }
    };
    mBleCallBack = new BluetoothGattCallback() {
      @Override
      public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
        super.onConnectionStateChange(gatt, status, newState);
        if (newState == BluetoothGatt.STATE_CONNECTED)
          gatt.discoverServices();
        else if (newState == BluetoothGatt.STATE_DISCONNECTED)
          pushConnectedState(BluetoothMsg.CONNECT_STATE_UNCONNECT);
      }

      @Override
      public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
        super.onCharacteristicWrite(gatt, characteristic, status);
      }

      @Override
      public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
        super.onDescriptorWrite(gatt, descriptor, status);
      }

      @Override
      public void onServicesDiscovered(BluetoothGatt gatt, int status) {
        super.onServicesDiscovered(gatt, status);
        boolean flag1 = false;
        boolean flag2 = false;
        boolean flag3 = false;
        BluetoothGattService service = gatt.getService(UUID.fromString(serviceUuid));
        if (service != null) {
          BluetoothGattCharacteristic characteristic = service.getCharacteristic(UUID.fromString(characteristicUuid));
          if (characteristic != null) {
            flag1 = gatt.setCharacteristicNotification(characteristic, true);
            BluetoothGattDescriptor descriptor = characteristic.getDescriptor(UUID.fromString(descriptorUuid));
            if (descriptor != null) {
              flag2 = descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
              flag3 = gatt.writeDescriptor(descriptor);
            }
          }
        }
        if (flag1 && flag2 && flag3) {
          mConnectedDevice = gatt.getDevice();
          pushConnectedState(BluetoothMsg.CONNECT_STATE_CONNECTED);
        } else {
          pushConnectedState(BluetoothMsg.CONNECT_STATE_FAIL);
          disconnectBle();
        }
      }

      @Override
      public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
        super.onCharacteristicChanged(gatt, characteristic);
        byte[] values = characteristic.getValue();
        String str = new String(values);
        Log.d(TAG, "BLE蓝牙获取数据：" + str);
        pushReadMsg(str);
      }

      @Override
      public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
        super.onCharacteristicRead(gatt, characteristic, status);
      }
    };
  }

  @Override
  public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
    Log.d(TAG, "************插件注册***********onAttachedToEngine");
    init(flutterPluginBinding.getApplicationContext());
    channel = new MethodChannel(flutterPluginBinding.getFlutterEngine().getDartExecutor(), "plugin.bgi.flutterbluetooth");
    channel.setMethodCallHandler(this);
    eventChannel = new EventChannel(flutterPluginBinding.getFlutterEngine().getDartExecutor(), "plugin.bgi.flutterbluetooth_scan_bluetooth");
    eventChannel.setStreamHandler(this);
  }


  // This static function is optional and equivalent to onAttachedToEngine. It supports the old
  // pre-Flutter-1.12 Android projects. You are encouraged to continue supporting
  // plugin registration via this function while apps migrate to use the new Android APIs
  // post-flutter-1.12 via https://flutter.dev/go/android-project-migration.
  //
  // It is encouraged to share logic between onAttachedToEngine and registerWith to keep
  // them functionally equivalent. Only one of onAttachedToEngine or registerWith will be called
  // depending on the user's project. onAttachedToEngine or registerWith must both be defined
  // in the same class.
  public static void registerWith(Registrar registrar) {
    Log.d(TAG, "************插件注册***********registerWith");
    final MethodChannel channel = new MethodChannel(registrar.messenger(), "plugin.bgi.flutterbluetooth");
    final EventChannel eventChannel = new EventChannel(registrar.messenger(), "plugin.bgi.flutterbluetooth_scan_bluetooth");
    FlutterbluetoothPlugin plugin = new FlutterbluetoothPlugin();
    plugin.init(registrar.context());
    channel.setMethodCallHandler(plugin);
    eventChannel.setStreamHandler(plugin);
  }

  @Override
  public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
    switch (call.method){
      case "blueState":
        result.success(blueState());
        break;
      case "startDiscovery":
        startDiscovery();
        break;
      case "connect":
        connect(((String) call.argument("address")), ((String)call.argument("uuid")));
        break;
      case "bleConnect":
        bleConnect(((String)call.argument("address")), ((String)call.argument("serviceUuid")), ((String)call.argument("characteristicUuid")), ((String)call.argument("descriptorUuid")));
        break;
      case "openBluetooth":
        openBlueTooth();
        break;
      case "cancelDiscovery":
        cancelDiscovery();
        break;
      case "readyRead":
        readyRead();
        break;
      case "send":
        send(((String)call.argument("msg")));
        break;
      case "disconnectWithPushState":
        disconnectWithPushState();
        break;
      case "disconnectBleWithPushState":
        disconnectBleWithPushState();
        break;
      case "disconnect":
        disconnect();
        break;
      case "disconnectBle":
        disconnectBle();
        break;
      default:
        result.notImplemented();
        break;
    }
  }

  @Override
  public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
    channel.setMethodCallHandler(null);
  }

  @Override
  public void onListen(Object o, EventChannel.EventSink mEventSink) {
    Log.d(TAG, "注册蓝牙监听");
    this.mEventSink = mEventSink;
    IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
    filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
    filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
    filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
    context.registerReceiver(mReceiver, filter);
  }

  @Override
  public void onCancel(Object o) {
    Log.d(TAG, "取消监听蓝牙信息");
    context.unregisterReceiver(mReceiver);
    eventChannel.setStreamHandler(null);
  }

  //获取蓝牙状态 -1 设备不支持蓝牙 10 关闭 11 正在打开 12 打开 13 正在关闭
  @TargetApi(Build.VERSION_CODES.ECLAIR)
  private int blueState() {
    if (mBluetoothAdapter == null) return -1;
    return mBluetoothAdapter.getState();
  }

  //开启蓝牙
  @TargetApi(Build.VERSION_CODES.ECLAIR)
  private void openBlueTooth() {
    Log.d(TAG, "打开蓝牙");
    // 没有打开蓝牙
    if (!mBluetoothAdapter.isEnabled()) {
      Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
      enableBtIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
      context.startActivity(enableBtIntent);
    }
  }

  //蓝牙扫描
  @TargetApi(Build.VERSION_CODES.ECLAIR)
  private void startDiscovery() {
    Log.d(TAG, "蓝牙扫描");
    mapBt.clear();
    mBluetoothAdapter.startDiscovery();
  }

  //取消扫描
  @TargetApi(Build.VERSION_CODES.ECLAIR)
  private void cancelDiscovery() {
    Log.d(TAG, "取消蓝牙扫描");
    if (mBluetoothAdapter.isDiscovering())
      mBluetoothAdapter.cancelDiscovery();
  }

  // 当 Discovery 发现了一个设备
  @TargetApi(Build.VERSION_CODES.ECLAIR)
  void pushDevice(Intent intent) {
    // 从 Intent 中获取发现的 BluetoothDevice
    Log.d(TAG, "推送蓝牙设备");
    BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
    mapBt.put(device.getAddress(), device);
    mEventSink.success(BluetoothMsg.createDeviceJson(device.getName(), device.getAddress()));
  }

  //推送蓝牙扫描状态
  private void pushScanState(int code) {
    Log.d(TAG, "推送蓝牙扫描状态");
    mEventSink.success(BluetoothMsg.createScanStateJson(code));
  }

  //推送蓝牙状态
  @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
  private void pushBlueToothState(Intent intent) {
    Log.d(TAG, "推送蓝牙状态");
    disconnectBle();
    disconnect();
    int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
    mEventSink.success(BluetoothMsg.createStateJson(state));
  }

  @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
  private void connect(String address, String uuid) {
    Log.d(TAG, "蓝牙连接");
    disconnect();
    BluetoothDevice device = mapBt.get(address);
    if (device != null) {
      try {
        if (mBtSocket != null)
          mBtSocket.close();
        mBtSocket = device.createRfcommSocketToServiceRecord(UUID.fromString(uuid));
        mBtSocket.connect();
        if (mBtSocket.isConnected()) {
          mConnectedDevice = device;
          pushConnectedState(BluetoothMsg.CONNECT_STATE_CONNECTED);
        } else {
          pushConnectedState(BluetoothMsg.CONNECT_STATE_FAIL);
        }
      } catch (IOException e) {
        pushConnectedState(BluetoothMsg.CONNECT_STATE_FAIL);
      }
    } else {
      pushConnectedState(BluetoothMsg.CONNECT_STATE_FAIL);
    }
  }

  @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
  private void bleConnect(String address, String serviceUuid, String characteristicUuid, String descriptorUuid) {
    Log.d(TAG, "ble蓝牙连接");
    disconnectBle();
    BluetoothDevice device = mapBt.get(address);
    if(device == null){
      pushConnectedState(BluetoothMsg.CONNECT_STATE_FAIL);
      return;
    }
    this.serviceUuid = serviceUuid;
    this.characteristicUuid = characteristicUuid;
    this.descriptorUuid = descriptorUuid;
    mGatt = device.connectGatt(context, false, mBleCallBack);
  }

  private void disconnectBleWithPushState(){
    disconnectBle();
    pushConnectedState(BluetoothMsg.CONNECT_STATE_UNCONNECT);
  }

  @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
  private void disconnectBle() {
    Log.d(TAG, "关闭BLE蓝牙连接");
    serviceUuid = null;
    characteristicUuid = null;
    descriptorUuid = null;
    try {
      if (mGatt != null) {
        mGatt.disconnect();
        mGatt.close();
      }
    } catch (Exception e) {
    } finally {
      mGatt = null;
      mConnectedDevice = null;
    }
  }

  //推送连接状态
  private void pushConnectedState(final int connectedState) {
    uiHandler.post(new Runnable() {
      @TargetApi(Build.VERSION_CODES.ECLAIR)
      @Override
      public void run() {
          switch (connectedState) {
            case BluetoothMsg.CONNECT_STATE_CONNECTED:
              mEventSink.success(BluetoothMsg.createConnectedStateJson(connectedState, mConnectedDevice.getName(), mConnectedDevice.getAddress()));
              break;
            case BluetoothMsg.CONNECT_STATE_UNCONNECT:
            case BluetoothMsg.CONNECT_STATE_FAIL:
              mEventSink.success(BluetoothMsg.createConnectedStateJson(connectedState, null, null));
              break;
          }
      }
    });
  }

  //准备读取数据 0 蓝牙未连接好 1 准备读取数据失败
  @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
  private void readyRead() {
    if (mBtSocket == null || !mBtSocket.isConnected()) {
      disconnectWithPushState();
      return;
    }
    new Thread() {
      @Override
      public void run() {
        super.run();
        try {
          BufferedReader br = new BufferedReader(new InputStreamReader(mBtSocket.getInputStream()));
          while (mBtSocket.isConnected()) {
            String msg = br.readLine();
            pushReadMsg(msg);
          }
        } catch (Exception e) {
          disconnectWithPushState();
        }
      }
    }.start();
  }

  //推送读取的数据
  private void pushReadMsg(final String msg) {
    Log.d(TAG, "推送读取数据：" + msg);
    uiHandler.post(new Runnable() {
      @Override
      public void run() {
        mEventSink.success(BluetoothMsg.createReadMsgJson(msg));
      }
    });
  }
  //发送数据
  @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
  private void send(String msg) {
    Log.d(TAG, "发送数据：" + msg);
    if (mBtSocket != null && mBtSocket.isConnected()) {
      try {
        mBtSocket.getOutputStream().write((msg + "\n").getBytes("utf-8"));
        mBtSocket.getOutputStream().flush();
      } catch (Exception e) {
        disconnectWithPushState();
      }
    }else{
      disconnectWithPushState();
    }
  }

  private void disconnectWithPushState(){
    disconnect();
    pushConnectedState(BluetoothMsg.CONNECT_STATE_UNCONNECT);
  }

  private void disconnect() {
    Log.d(TAG, "关闭蓝牙连接");
    try {
      if (mBtSocket != null) {
        mBtSocket.close();
      }
    } catch (Exception e) {
    } finally {
      mConnectedDevice = null;
      mBtSocket = null;
    }
  }
}
